/*
 * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.security;

import java.io.*;
import java.util.*;

import static java.util.Locale.ENGLISH;

import java.lang.ref.*;
import java.lang.reflect.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * This class represents a "provider" for the
 * Java Security API, where a provider implements some or all parts of
 * Java Security. Services that a provider may implement include:
 *
 * <ul>
 *
 * <li>Algorithms (such as DSA, RSA, MD5 or SHA-1).
 *
 * <li>Key generation, conversion, and management facilities (such as for
 * algorithm-specific keys).
 *
 * </ul>
 *
 * <p>Each provider has a name and a version number, and is configured
 * in each runtime it is installed in.
 *
 * <p>See <a href =
 * "../../../technotes/guides/security/crypto/CryptoSpec.html#Provider">The Provider Class</a>
 * in the "Java Cryptography Architecture API Specification &amp; Reference"
 * for information about how a particular type of provider, the
 * cryptographic service provider, works and is installed. However,
 * please note that a provider can be used to implement any security
 * service in Java that uses a pluggable architecture with a choice
 * of implementations that fit underneath.
 *
 * <p>Some provider implementations may encounter unrecoverable internal
 * errors during their operation, for example a failure to communicate with a
 * security token. A {@link ProviderException} should be used to indicate
 * such errors.
 *
 * <p>The service type {@code Provider} is reserved for use by the
 * security framework. Services of this type cannot be added, removed,
 * or modified by applications.
 * The following attributes are automatically placed in each Provider object:
 * <table cellspacing=4>
 * <caption><b>Attributes Automatically Placed in a Provider Object</b></caption>
 * <tr><th>Name</th><th>Value</th>
 * <tr><td>{@code Provider.id name}</td>
 * <td>{@code String.valueOf(provider.getName())}</td>
 * <tr><td>{@code Provider.id version}</td>
 * <td>{@code String.valueOf(provider.getVersion())}</td>
 * <tr><td>{@code Provider.id info}</td>
 * <td>{@code String.valueOf(provider.getInfo())}</td>
 * <tr><td>{@code Provider.id className}</td>
 * <td>{@code provider.getClass().getName()}</td>
 * </table>
 *
 * @author Benjamin Renaud
 * @author Andreas Sterbenz
 */
public abstract class Provider extends Properties {

  // Declare serialVersionUID to be compatible with JDK1.1
  static final long serialVersionUID = -4298000515446427739L;

  private static final sun.security.util.Debug debug =
      sun.security.util.Debug.getInstance
          ("provider", "Provider");

  /**
   * The provider name.
   *
   * @serial
   */
  private String name;

  /**
   * A description of the provider and its services.
   *
   * @serial
   */
  private String info;

  /**
   * The provider version number.
   *
   * @serial
   */
  private double version;


  private transient Set<Map.Entry<Object, Object>> entrySet = null;
  private transient int entrySetCallCount = 0;

  private transient boolean initialized;

  /**
   * Constructs a provider with the specified name, version number,
   * and information.
   *
   * @param name the provider name.
   * @param version the provider version number.
   * @param info a description of the provider and its services.
   */
  protected Provider(String name, double version, String info) {
    this.name = name;
    this.version = version;
    this.info = info;
    putId();
    initialized = true;
  }

  /**
   * Returns the name of this provider.
   *
   * @return the name of this provider.
   */
  public String getName() {
    return name;
  }

  /**
   * Returns the version number for this provider.
   *
   * @return the version number for this provider.
   */
  public double getVersion() {
    return version;
  }

  /**
   * Returns a human-readable description of the provider and its
   * services.  This may return an HTML page, with relevant links.
   *
   * @return a description of the provider and its services.
   */
  public String getInfo() {
    return info;
  }

  /**
   * Returns a string with the name and the version number
   * of this provider.
   *
   * @return the string with the name and the version number for this provider.
   */
  public String toString() {
    return name + " version " + version;
  }

    /*
     * override the following methods to ensure that provider
     * information can only be changed if the caller has the appropriate
     * permissions.
     */

  /**
   * Clears this provider so that it no longer contains the properties
   * used to look up facilities implemented by the provider.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the string {@code "clearProviderProperties."+name}
   * (where {@code name} is the provider name) to see if it's ok to clear
   * this provider.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to clear this provider
   * @since 1.2
   */
  @Override
  public synchronized void clear() {
    check("clearProviderProperties." + name);
    if (debug != null) {
      debug.println("Remove " + name + " provider properties");
    }
    implClear();
  }

  /**
   * Reads a property list (key and element pairs) from the input stream.
   *
   * @param inStream the input stream.
   * @throws IOException if an error occurred when reading from the input stream.
   * @see java.util.Properties#load
   */
  @Override
  public synchronized void load(InputStream inStream) throws IOException {
    check("putProviderProperty." + name);
    if (debug != null) {
      debug.println("Load " + name + " provider properties");
    }
    Properties tempProperties = new Properties();
    tempProperties.load(inStream);
    implPutAll(tempProperties);
  }

  /**
   * Copies all of the mappings from the specified Map to this provider.
   * These mappings will replace any properties that this provider had
   * for any of the keys currently in the specified Map.
   *
   * @since 1.2
   */
  @Override
  public synchronized void putAll(Map<?, ?> t) {
    check("putProviderProperty." + name);
    if (debug != null) {
      debug.println("Put all " + name + " provider properties");
    }
    implPutAll(t);
  }

  /**
   * Returns an unmodifiable Set view of the property entries contained
   * in this Provider.
   *
   * @see java.util.Map.Entry
   * @since 1.2
   */
  @Override
  public synchronized Set<Map.Entry<Object, Object>> entrySet() {
    checkInitialized();
    if (entrySet == null) {
      if (entrySetCallCount++ == 0)  // Initial call
      {
        entrySet = Collections.unmodifiableMap(this).entrySet();
      } else {
        return super.entrySet();   // Recursive call
      }
    }

    // This exception will be thrown if the implementation of
    // Collections.unmodifiableMap.entrySet() is changed such that it
    // no longer calls entrySet() on the backing Map.  (Provider's
    // entrySet implementation depends on this "implementation detail",
    // which is unlikely to change.
    if (entrySetCallCount != 2) {
      throw new RuntimeException("Internal error.");
    }

    return entrySet;
  }

  /**
   * Returns an unmodifiable Set view of the property keys contained in
   * this provider.
   *
   * @since 1.2
   */
  @Override
  public Set<Object> keySet() {
    checkInitialized();
    return Collections.unmodifiableSet(super.keySet());
  }

  /**
   * Returns an unmodifiable Collection view of the property values
   * contained in this provider.
   *
   * @since 1.2
   */
  @Override
  public Collection<Object> values() {
    checkInitialized();
    return Collections.unmodifiableCollection(super.values());
  }

  /**
   * Sets the {@code key} property to have the specified
   * {@code value}.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the string {@code "putProviderProperty."+name},
   * where {@code name} is the provider name, to see if it's ok to set this
   * provider's property values.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values.
   * @since 1.2
   */
  @Override
  public synchronized Object put(Object key, Object value) {
    check("putProviderProperty." + name);
    if (debug != null) {
      debug.println("Set " + name + " provider property [" +
          key + "/" + value + "]");
    }
    return implPut(key, value);
  }

  /**
   * If the specified key is not already associated with a value (or is mapped
   * to {@code null}) associates it with the given value and returns
   * {@code null}, else returns the current value.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the string {@code "putProviderProperty."+name},
   * where {@code name} is the provider name, to see if it's ok to set this
   * provider's property values.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values.
   * @since 1.8
   */
  @Override
  public synchronized Object putIfAbsent(Object key, Object value) {
    check("putProviderProperty." + name);
    if (debug != null) {
      debug.println("Set " + name + " provider property [" +
          key + "/" + value + "]");
    }
    return implPutIfAbsent(key, value);
  }

  /**
   * Removes the {@code key} property (and its corresponding
   * {@code value}).
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the string {@code "removeProviderProperty."+name},
   * where {@code name} is the provider name, to see if it's ok to remove this
   * provider's properties.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to remove this provider's
   * properties.
   * @since 1.2
   */
  @Override
  public synchronized Object remove(Object key) {
    check("removeProviderProperty." + name);
    if (debug != null) {
      debug.println("Remove " + name + " provider property " + key);
    }
    return implRemove(key);
  }

  /**
   * Removes the entry for the specified key only if it is currently
   * mapped to the specified value.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the string {@code "removeProviderProperty."+name},
   * where {@code name} is the provider name, to see if it's ok to remove this
   * provider's properties.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to remove this provider's
   * properties.
   * @since 1.8
   */
  @Override
  public synchronized boolean remove(Object key, Object value) {
    check("removeProviderProperty." + name);
    if (debug != null) {
      debug.println("Remove " + name + " provider property " + key);
    }
    return implRemove(key, value);
  }

  /**
   * Replaces the entry for the specified key only if currently
   * mapped to the specified value.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the string {@code "putProviderProperty."+name},
   * where {@code name} is the provider name, to see if it's ok to set this
   * provider's property values.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values.
   * @since 1.8
   */
  @Override
  public synchronized boolean replace(Object key, Object oldValue,
      Object newValue) {
    check("putProviderProperty." + name);

    if (debug != null) {
      debug.println("Replace " + name + " provider property " + key);
    }
    return implReplace(key, oldValue, newValue);
  }

  /**
   * Replaces the entry for the specified key only if it is
   * currently mapped to some value.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the string {@code "putProviderProperty."+name},
   * where {@code name} is the provider name, to see if it's ok to set this
   * provider's property values.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values.
   * @since 1.8
   */
  @Override
  public synchronized Object replace(Object key, Object value) {
    check("putProviderProperty." + name);

    if (debug != null) {
      debug.println("Replace " + name + " provider property " + key);
    }
    return implReplace(key, value);
  }

  /**
   * Replaces each entry's value with the result of invoking the given
   * function on that entry, in the order entries are returned by an entry
   * set iterator, until all entries have been processed or the function
   * throws an exception.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the string {@code "putProviderProperty."+name},
   * where {@code name} is the provider name, to see if it's ok to set this
   * provider's property values.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values.
   * @since 1.8
   */
  @Override
  public synchronized void replaceAll(
      BiFunction<? super Object, ? super Object, ? extends Object> function) {
    check("putProviderProperty." + name);

    if (debug != null) {
      debug.println("ReplaceAll " + name + " provider property ");
    }
    implReplaceAll(function);
  }

  /**
   * Attempts to compute a mapping for the specified key and its
   * current mapped value (or {@code null} if there is no current
   * mapping).
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the strings {@code "putProviderProperty."+name}
   * and {@code "removeProviderProperty."+name}, where {@code name} is the
   * provider name, to see if it's ok to set this provider's property values
   * and remove this provider's properties.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values or
   * remove properties.
   * @since 1.8
   */
  @Override
  public synchronized Object compute(Object key,
      BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
    check("putProviderProperty." + name);
    check("removeProviderProperty" + name);

    if (debug != null) {
      debug.println("Compute " + name + " provider property " + key);
    }
    return implCompute(key, remappingFunction);
  }

  /**
   * If the specified key is not already associated with a value (or
   * is mapped to {@code null}), attempts to compute its value using
   * the given mapping function and enters it into this map unless
   * {@code null}.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the strings {@code "putProviderProperty."+name}
   * and {@code "removeProviderProperty."+name}, where {@code name} is the
   * provider name, to see if it's ok to set this provider's property values
   * and remove this provider's properties.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values and
   * remove properties.
   * @since 1.8
   */
  @Override
  public synchronized Object computeIfAbsent(Object key,
      Function<? super Object, ? extends Object> mappingFunction) {
    check("putProviderProperty." + name);
    check("removeProviderProperty" + name);

    if (debug != null) {
      debug.println("ComputeIfAbsent " + name + " provider property " +
          key);
    }
    return implComputeIfAbsent(key, mappingFunction);
  }

  /**
   * If the value for the specified key is present and non-null, attempts to
   * compute a new mapping given the key and its current mapped value.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the strings {@code "putProviderProperty."+name}
   * and {@code "removeProviderProperty."+name}, where {@code name} is the
   * provider name, to see if it's ok to set this provider's property values
   * and remove this provider's properties.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values or
   * remove properties.
   * @since 1.8
   */
  @Override
  public synchronized Object computeIfPresent(Object key,
      BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
    check("putProviderProperty." + name);
    check("removeProviderProperty" + name);

    if (debug != null) {
      debug.println("ComputeIfPresent " + name + " provider property " +
          key);
    }
    return implComputeIfPresent(key, remappingFunction);
  }

  /**
   * If the specified key is not already associated with a value or is
   * associated with null, associates it with the given value. Otherwise,
   * replaces the value with the results of the given remapping function,
   * or removes if the result is null. This method may be of use when
   * combining multiple mapped values for a key.
   *
   * <p>If a security manager is enabled, its {@code checkSecurityAccess}
   * method is called with the strings {@code "putProviderProperty."+name}
   * and {@code "removeProviderProperty."+name}, where {@code name} is the
   * provider name, to see if it's ok to set this provider's property values
   * and remove this provider's properties.
   *
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values or
   * remove properties.
   * @since 1.8
   */
  @Override
  public synchronized Object merge(Object key, Object value,
      BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
    check("putProviderProperty." + name);
    check("removeProviderProperty" + name);

    if (debug != null) {
      debug.println("Merge " + name + " provider property " + key);
    }
    return implMerge(key, value, remappingFunction);
  }

  // let javadoc show doc from superclass
  @Override
  public Object get(Object key) {
    checkInitialized();
    return super.get(key);
  }

  /**
   * @since 1.8
   */
  @Override
  public synchronized Object getOrDefault(Object key, Object defaultValue) {
    checkInitialized();
    return super.getOrDefault(key, defaultValue);
  }

  /**
   * @since 1.8
   */
  @Override
  public synchronized void forEach(BiConsumer<? super Object, ? super Object> action) {
    checkInitialized();
    super.forEach(action);
  }

  // let javadoc show doc from superclass
  @Override
  public Enumeration<Object> keys() {
    checkInitialized();
    return super.keys();
  }

  // let javadoc show doc from superclass
  @Override
  public Enumeration<Object> elements() {
    checkInitialized();
    return super.elements();
  }

  // let javadoc show doc from superclass
  public String getProperty(String key) {
    checkInitialized();
    return super.getProperty(key);
  }

  private void checkInitialized() {
    if (!initialized) {
      throw new IllegalStateException();
    }
  }

  private void check(String directive) {
    checkInitialized();
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
      security.checkSecurityAccess(directive);
    }
  }

  // legacy properties changed since last call to any services method?
  private transient boolean legacyChanged;
  // serviceMap changed since last call to getServices()
  private transient boolean servicesChanged;

  // Map<String,String>
  private transient Map<String, String> legacyStrings;

  // Map<ServiceKey,Service>
  // used for services added via putService(), initialized on demand
  private transient Map<ServiceKey, Service> serviceMap;

  // Map<ServiceKey,Service>
  // used for services added via legacy methods, init on demand
  private transient Map<ServiceKey, Service> legacyMap;

  // Set<Service>
  // Unmodifiable set of all services. Initialized on demand.
  private transient Set<Service> serviceSet;

  // register the id attributes for this provider
  // this is to ensure that equals() and hashCode() do not incorrectly
  // report to different provider objects as the same
  private void putId() {
    // note: name and info may be null
    super.put("Provider.id name", String.valueOf(name));
    super.put("Provider.id version", String.valueOf(version));
    super.put("Provider.id info", String.valueOf(info));
    super.put("Provider.id className", this.getClass().getName());
  }

  private void readObject(ObjectInputStream in)
      throws IOException, ClassNotFoundException {
    Map<Object, Object> copy = new HashMap<>();
    for (Map.Entry<Object, Object> entry : super.entrySet()) {
      copy.put(entry.getKey(), entry.getValue());
    }
    defaults = null;
    in.defaultReadObject();
    implClear();
    initialized = true;
    putAll(copy);
  }

  private boolean checkLegacy(Object key) {
    String keyString = (String) key;
    if (keyString.startsWith("Provider.")) {
      return false;
    }

    legacyChanged = true;
    if (legacyStrings == null) {
      legacyStrings = new LinkedHashMap<String, String>();
    }
    return true;
  }

  /**
   * Copies all of the mappings from the specified Map to this provider.
   * Internal method to be called AFTER the security check has been
   * performed.
   */
  private void implPutAll(Map<?, ?> t) {
    for (Map.Entry<?, ?> e : t.entrySet()) {
      implPut(e.getKey(), e.getValue());
    }
  }

  private Object implRemove(Object key) {
    if (key instanceof String) {
      if (!checkLegacy(key)) {
        return null;
      }
      legacyStrings.remove((String) key);
    }
    return super.remove(key);
  }

  private boolean implRemove(Object key, Object value) {
    if (key instanceof String && value instanceof String) {
      if (!checkLegacy(key)) {
        return false;
      }
      legacyStrings.remove((String) key, value);
    }
    return super.remove(key, value);
  }

  private boolean implReplace(Object key, Object oldValue, Object newValue) {
    if ((key instanceof String) && (oldValue instanceof String) &&
        (newValue instanceof String)) {
      if (!checkLegacy(key)) {
        return false;
      }
      legacyStrings.replace((String) key, (String) oldValue,
          (String) newValue);
    }
    return super.replace(key, oldValue, newValue);
  }

  private Object implReplace(Object key, Object value) {
    if ((key instanceof String) && (value instanceof String)) {
      if (!checkLegacy(key)) {
        return null;
      }
      legacyStrings.replace((String) key, (String) value);
    }
    return super.replace(key, value);
  }

  private void implReplaceAll(
      BiFunction<? super Object, ? super Object, ? extends Object> function) {
    legacyChanged = true;
    if (legacyStrings == null) {
      legacyStrings = new LinkedHashMap<String, String>();
    } else {
      legacyStrings
          .replaceAll((BiFunction<? super String, ? super String, ? extends String>) function);
    }
    super.replaceAll(function);
  }


  private Object implMerge(Object key, Object value,
      BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
    if ((key instanceof String) && (value instanceof String)) {
      if (!checkLegacy(key)) {
        return null;
      }
      legacyStrings.merge((String) key, (String) value,
          (BiFunction<? super String, ? super String, ? extends String>) remappingFunction);
    }
    return super.merge(key, value, remappingFunction);
  }

  private Object implCompute(Object key,
      BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
    if (key instanceof String) {
      if (!checkLegacy(key)) {
        return null;
      }
      legacyStrings.computeIfAbsent((String) key,
          (Function<? super String, ? extends String>) remappingFunction);
    }
    return super.compute(key, remappingFunction);
  }

  private Object implComputeIfAbsent(Object key,
      Function<? super Object, ? extends Object> mappingFunction) {
    if (key instanceof String) {
      if (!checkLegacy(key)) {
        return null;
      }
      legacyStrings.computeIfAbsent((String) key,
          (Function<? super String, ? extends String>) mappingFunction);
    }
    return super.computeIfAbsent(key, mappingFunction);
  }

  private Object implComputeIfPresent(Object key,
      BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
    if (key instanceof String) {
      if (!checkLegacy(key)) {
        return null;
      }
      legacyStrings.computeIfPresent((String) key,
          (BiFunction<? super String, ? super String, ? extends String>) remappingFunction);
    }
    return super.computeIfPresent(key, remappingFunction);
  }

  private Object implPut(Object key, Object value) {
    if ((key instanceof String) && (value instanceof String)) {
      if (!checkLegacy(key)) {
        return null;
      }
      legacyStrings.put((String) key, (String) value);
    }
    return super.put(key, value);
  }

  private Object implPutIfAbsent(Object key, Object value) {
    if ((key instanceof String) && (value instanceof String)) {
      if (!checkLegacy(key)) {
        return null;
      }
      legacyStrings.putIfAbsent((String) key, (String) value);
    }
    return super.putIfAbsent(key, value);
  }

  private void implClear() {
    if (legacyStrings != null) {
      legacyStrings.clear();
    }
    if (legacyMap != null) {
      legacyMap.clear();
    }
    if (serviceMap != null) {
      serviceMap.clear();
    }
    legacyChanged = false;
    servicesChanged = false;
    serviceSet = null;
    super.clear();
    putId();
  }

  // used as key in the serviceMap and legacyMap HashMaps
  private static class ServiceKey {

    private final String type;
    private final String algorithm;
    private final String originalAlgorithm;

    private ServiceKey(String type, String algorithm, boolean intern) {
      this.type = type;
      this.originalAlgorithm = algorithm;
      algorithm = algorithm.toUpperCase(ENGLISH);
      this.algorithm = intern ? algorithm.intern() : algorithm;
    }

    public int hashCode() {
      return type.hashCode() + algorithm.hashCode();
    }

    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj instanceof ServiceKey == false) {
        return false;
      }
      ServiceKey other = (ServiceKey) obj;
      return this.type.equals(other.type)
          && this.algorithm.equals(other.algorithm);
    }

    boolean matches(String type, String algorithm) {
      return (this.type == type) && (this.originalAlgorithm == algorithm);
    }
  }

  /**
   * Ensure all the legacy String properties are fully parsed into
   * service objects.
   */
  private void ensureLegacyParsed() {
    if ((legacyChanged == false) || (legacyStrings == null)) {
      return;
    }
    serviceSet = null;
    if (legacyMap == null) {
      legacyMap = new LinkedHashMap<ServiceKey, Service>();
    } else {
      legacyMap.clear();
    }
    for (Map.Entry<String, String> entry : legacyStrings.entrySet()) {
      parseLegacyPut(entry.getKey(), entry.getValue());
    }
    removeInvalidServices(legacyMap);
    legacyChanged = false;
  }

  /**
   * Remove all invalid services from the Map. Invalid services can only
   * occur if the legacy properties are inconsistent or incomplete.
   */
  private void removeInvalidServices(Map<ServiceKey, Service> map) {
    for (Iterator<Map.Entry<ServiceKey, Service>> t =
        map.entrySet().iterator(); t.hasNext(); ) {
      Service s = t.next().getValue();
      if (s.isValid() == false) {
        t.remove();
      }
    }
  }

  private String[] getTypeAndAlgorithm(String key) {
    int i = key.indexOf(".");
    if (i < 1) {
      if (debug != null) {
        debug.println("Ignoring invalid entry in provider "
            + name + ":" + key);
      }
      return null;
    }
    String type = key.substring(0, i);
    String alg = key.substring(i + 1);
    return new String[]{type, alg};
  }

  private final static String ALIAS_PREFIX = "Alg.Alias.";
  private final static String ALIAS_PREFIX_LOWER = "alg.alias.";
  private final static int ALIAS_LENGTH = ALIAS_PREFIX.length();

  private void parseLegacyPut(String name, String value) {
    if (name.toLowerCase(ENGLISH).startsWith(ALIAS_PREFIX_LOWER)) {
      // e.g. put("Alg.Alias.MessageDigest.SHA", "SHA-1");
      // aliasKey ~ MessageDigest.SHA
      String stdAlg = value;
      String aliasKey = name.substring(ALIAS_LENGTH);
      String[] typeAndAlg = getTypeAndAlgorithm(aliasKey);
      if (typeAndAlg == null) {
        return;
      }
      String type = getEngineName(typeAndAlg[0]);
      String aliasAlg = typeAndAlg[1].intern();
      ServiceKey key = new ServiceKey(type, stdAlg, true);
      Service s = legacyMap.get(key);
      if (s == null) {
        s = new Service(this);
        s.type = type;
        s.algorithm = stdAlg;
        legacyMap.put(key, s);
      }
      legacyMap.put(new ServiceKey(type, aliasAlg, true), s);
      s.addAlias(aliasAlg);
    } else {
      String[] typeAndAlg = getTypeAndAlgorithm(name);
      if (typeAndAlg == null) {
        return;
      }
      int i = typeAndAlg[1].indexOf(' ');
      if (i == -1) {
        // e.g. put("MessageDigest.SHA-1", "sun.security.provider.SHA");
        String type = getEngineName(typeAndAlg[0]);
        String stdAlg = typeAndAlg[1].intern();
        String className = value;
        ServiceKey key = new ServiceKey(type, stdAlg, true);
        Service s = legacyMap.get(key);
        if (s == null) {
          s = new Service(this);
          s.type = type;
          s.algorithm = stdAlg;
          legacyMap.put(key, s);
        }
        s.className = className;
      } else { // attribute
        // e.g. put("MessageDigest.SHA-1 ImplementedIn", "Software");
        String attributeValue = value;
        String type = getEngineName(typeAndAlg[0]);
        String attributeString = typeAndAlg[1];
        String stdAlg = attributeString.substring(0, i).intern();
        String attributeName = attributeString.substring(i + 1);
        // kill additional spaces
        while (attributeName.startsWith(" ")) {
          attributeName = attributeName.substring(1);
        }
        attributeName = attributeName.intern();
        ServiceKey key = new ServiceKey(type, stdAlg, true);
        Service s = legacyMap.get(key);
        if (s == null) {
          s = new Service(this);
          s.type = type;
          s.algorithm = stdAlg;
          legacyMap.put(key, s);
        }
        s.addAttribute(attributeName, attributeValue);
      }
    }
  }

  /**
   * Get the service describing this Provider's implementation of the
   * specified type of this algorithm or alias. If no such
   * implementation exists, this method returns null. If there are two
   * matching services, one added to this provider using
   * {@link #putService putService()} and one added via {@link #put put()},
   * the service added via {@link #putService putService()} is returned.
   *
   * @param type the type of {@link Service service} requested (for example, {@code MessageDigest})
   * @param algorithm the case insensitive algorithm name (or alternate alias) of the service
   * requested (for example, {@code SHA-1})
   * @return the service describing this Provider's matching service or null if no such service
   * exists
   * @throws NullPointerException if type or algorithm is null
   * @since 1.5
   */
  public synchronized Service getService(String type, String algorithm) {
    checkInitialized();
    // avoid allocating a new key object if possible
    ServiceKey key = previousKey;
    if (key.matches(type, algorithm) == false) {
      key = new ServiceKey(type, algorithm, false);
      previousKey = key;
    }
    if (serviceMap != null) {
      Service service = serviceMap.get(key);
      if (service != null) {
        return service;
      }
    }
    ensureLegacyParsed();
    return (legacyMap != null) ? legacyMap.get(key) : null;
  }

  // ServiceKey from previous getService() call
  // by re-using it if possible we avoid allocating a new object
  // and the toUpperCase() call.
  // re-use will occur e.g. as the framework traverses the provider
  // list and queries each provider with the same values until it finds
  // a matching service
  private static volatile ServiceKey previousKey =
      new ServiceKey("", "", false);

  /**
   * Get an unmodifiable Set of all services supported by
   * this Provider.
   *
   * @return an unmodifiable Set of all services supported by this Provider
   * @since 1.5
   */
  public synchronized Set<Service> getServices() {
    checkInitialized();
    if (legacyChanged || servicesChanged) {
      serviceSet = null;
    }
    if (serviceSet == null) {
      ensureLegacyParsed();
      Set<Service> set = new LinkedHashSet<>();
      if (serviceMap != null) {
        set.addAll(serviceMap.values());
      }
      if (legacyMap != null) {
        set.addAll(legacyMap.values());
      }
      serviceSet = Collections.unmodifiableSet(set);
      servicesChanged = false;
    }
    return serviceSet;
  }

  /**
   * Add a service. If a service of the same type with the same algorithm
   * name exists and it was added using {@link #putService putService()},
   * it is replaced by the new service.
   * This method also places information about this service
   * in the provider's Hashtable values in the format described in the
   * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
   * Java Cryptography Architecture API Specification &amp; Reference </a>.
   *
   * <p>Also, if there is a security manager, its
   * {@code checkSecurityAccess} method is called with the string
   * {@code "putProviderProperty."+name}, where {@code name} is
   * the provider name, to see if it's ok to set this provider's property
   * values. If the default implementation of {@code checkSecurityAccess}
   * is used (that is, that method is not overriden), then this results in
   * a call to the security manager's {@code checkPermission} method with
   * a {@code SecurityPermission("putProviderProperty."+name)}
   * permission.
   *
   * @param s the Service to add
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to set property values.
   * @throws NullPointerException if s is null
   * @since 1.5
   */
  protected synchronized void putService(Service s) {
    check("putProviderProperty." + name);
    if (debug != null) {
      debug.println(name + ".putService(): " + s);
    }
    if (s == null) {
      throw new NullPointerException();
    }
    if (s.getProvider() != this) {
      throw new IllegalArgumentException
          ("service.getProvider() must match this Provider object");
    }
    if (serviceMap == null) {
      serviceMap = new LinkedHashMap<ServiceKey, Service>();
    }
    servicesChanged = true;
    String type = s.getType();
    String algorithm = s.getAlgorithm();
    ServiceKey key = new ServiceKey(type, algorithm, true);
    // remove existing service
    implRemoveService(serviceMap.get(key));
    serviceMap.put(key, s);
    for (String alias : s.getAliases()) {
      serviceMap.put(new ServiceKey(type, alias, true), s);
    }
    putPropertyStrings(s);
  }

  /**
   * Put the string properties for this Service in this Provider's
   * Hashtable.
   */
  private void putPropertyStrings(Service s) {
    String type = s.getType();
    String algorithm = s.getAlgorithm();
    // use super() to avoid permission check and other processing
    super.put(type + "." + algorithm, s.getClassName());
    for (String alias : s.getAliases()) {
      super.put(ALIAS_PREFIX + type + "." + alias, algorithm);
    }
    for (Map.Entry<UString, String> entry : s.attributes.entrySet()) {
      String key = type + "." + algorithm + " " + entry.getKey();
      super.put(key, entry.getValue());
    }
  }

  /**
   * Remove the string properties for this Service from this Provider's
   * Hashtable.
   */
  private void removePropertyStrings(Service s) {
    String type = s.getType();
    String algorithm = s.getAlgorithm();
    // use super() to avoid permission check and other processing
    super.remove(type + "." + algorithm);
    for (String alias : s.getAliases()) {
      super.remove(ALIAS_PREFIX + type + "." + alias);
    }
    for (Map.Entry<UString, String> entry : s.attributes.entrySet()) {
      String key = type + "." + algorithm + " " + entry.getKey();
      super.remove(key);
    }
  }

  /**
   * Remove a service previously added using
   * {@link #putService putService()}. The specified service is removed from
   * this provider. It will no longer be returned by
   * {@link #getService getService()} and its information will be removed
   * from this provider's Hashtable.
   *
   * <p>Also, if there is a security manager, its
   * {@code checkSecurityAccess} method is called with the string
   * {@code "removeProviderProperty."+name}, where {@code name} is
   * the provider name, to see if it's ok to remove this provider's
   * properties. If the default implementation of
   * {@code checkSecurityAccess} is used (that is, that method is not
   * overriden), then this results in a call to the security manager's
   * {@code checkPermission} method with a
   * {@code SecurityPermission("removeProviderProperty."+name)}
   * permission.
   *
   * @param s the Service to be removed
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to remove this provider's
   * properties.
   * @throws NullPointerException if s is null
   * @since 1.5
   */
  protected synchronized void removeService(Service s) {
    check("removeProviderProperty." + name);
    if (debug != null) {
      debug.println(name + ".removeService(): " + s);
    }
    if (s == null) {
      throw new NullPointerException();
    }
    implRemoveService(s);
  }

  private void implRemoveService(Service s) {
    if ((s == null) || (serviceMap == null)) {
      return;
    }
    String type = s.getType();
    String algorithm = s.getAlgorithm();
    ServiceKey key = new ServiceKey(type, algorithm, false);
    Service oldService = serviceMap.get(key);
    if (s != oldService) {
      return;
    }
    servicesChanged = true;
    serviceMap.remove(key);
    for (String alias : s.getAliases()) {
      serviceMap.remove(new ServiceKey(type, alias, false));
    }
    removePropertyStrings(s);
  }

  // Wrapped String that behaves in a case insensitive way for equals/hashCode
  private static class UString {

    final String string;
    final String lowerString;

    UString(String s) {
      this.string = s;
      this.lowerString = s.toLowerCase(ENGLISH);
    }

    public int hashCode() {
      return lowerString.hashCode();
    }

    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj instanceof UString == false) {
        return false;
      }
      UString other = (UString) obj;
      return lowerString.equals(other.lowerString);
    }

    public String toString() {
      return string;
    }
  }

  // describe relevant properties of a type of engine
  private static class EngineDescription {

    final String name;
    final boolean supportsParameter;
    final String constructorParameterClassName;
    private volatile Class<?> constructorParameterClass;

    EngineDescription(String name, boolean sp, String paramName) {
      this.name = name;
      this.supportsParameter = sp;
      this.constructorParameterClassName = paramName;
    }

    Class<?> getConstructorParameterClass() throws ClassNotFoundException {
      Class<?> clazz = constructorParameterClass;
      if (clazz == null) {
        clazz = Class.forName(constructorParameterClassName);
        constructorParameterClass = clazz;
      }
      return clazz;
    }
  }

  // built in knowledge of the engine types shipped as part of the JDK
  private static final Map<String, EngineDescription> knownEngines;

  private static void addEngine(String name, boolean sp, String paramName) {
    EngineDescription ed = new EngineDescription(name, sp, paramName);
    // also index by canonical name to avoid toLowerCase() for some lookups
    knownEngines.put(name.toLowerCase(ENGLISH), ed);
    knownEngines.put(name, ed);
  }

  static {
    knownEngines = new HashMap<String, EngineDescription>();
    // JCA
    addEngine("AlgorithmParameterGenerator", false, null);
    addEngine("AlgorithmParameters", false, null);
    addEngine("KeyFactory", false, null);
    addEngine("KeyPairGenerator", false, null);
    addEngine("KeyStore", false, null);
    addEngine("MessageDigest", false, null);
    addEngine("SecureRandom", false, null);
    addEngine("Signature", true, null);
    addEngine("CertificateFactory", false, null);
    addEngine("CertPathBuilder", false, null);
    addEngine("CertPathValidator", false, null);
    addEngine("CertStore", false,
        "java.security.cert.CertStoreParameters");
    // JCE
    addEngine("Cipher", true, null);
    addEngine("ExemptionMechanism", false, null);
    addEngine("Mac", true, null);
    addEngine("KeyAgreement", true, null);
    addEngine("KeyGenerator", false, null);
    addEngine("SecretKeyFactory", false, null);
    // JSSE
    addEngine("KeyManagerFactory", false, null);
    addEngine("SSLContext", false, null);
    addEngine("TrustManagerFactory", false, null);
    // JGSS
    addEngine("GssApiMechanism", false, null);
    // SASL
    addEngine("SaslClientFactory", false, null);
    addEngine("SaslServerFactory", false, null);
    // POLICY
    addEngine("Policy", false,
        "java.security.Policy$Parameters");
    // CONFIGURATION
    addEngine("Configuration", false,
        "javax.security.auth.login.Configuration$Parameters");
    // XML DSig
    addEngine("XMLSignatureFactory", false, null);
    addEngine("KeyInfoFactory", false, null);
    addEngine("TransformService", false, null);
    // Smart Card I/O
    addEngine("TerminalFactory", false,
        "java.lang.Object");
  }

  // get the "standard" (mixed-case) engine name for arbitary case engine name
  // if there is no known engine by that name, return s
  private static String getEngineName(String s) {
    // try original case first, usually correct
    EngineDescription e = knownEngines.get(s);
    if (e == null) {
      e = knownEngines.get(s.toLowerCase(ENGLISH));
    }
    return (e == null) ? s : e.name;
  }

  /**
   * The description of a security service. It encapsulates the properties
   * of a service and contains a factory method to obtain new implementation
   * instances of this service.
   *
   * <p>Each service has a provider that offers the service, a type,
   * an algorithm name, and the name of the class that implements the
   * service. Optionally, it also includes a list of alternate algorithm
   * names for this service (aliases) and attributes, which are a map of
   * (name, value) String pairs.
   *
   * <p>This class defines the methods {@link #supportsParameter
   * supportsParameter()} and {@link #newInstance newInstance()}
   * which are used by the Java security framework when it searches for
   * suitable services and instantiates them. The valid arguments to those
   * methods depend on the type of service. For the service types defined
   * within Java SE, see the
   * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
   * Java Cryptography Architecture API Specification &amp; Reference </a>
   * for the valid values.
   * Note that components outside of Java SE can define additional types of
   * services and their behavior.
   *
   * <p>Instances of this class are immutable.
   *
   * @since 1.5
   */
  public static class Service {

    private String type, algorithm, className;
    private final Provider provider;
    private List<String> aliases;
    private Map<UString, String> attributes;

    // Reference to the cached implementation Class object
    private volatile Reference<Class<?>> classRef;

    // flag indicating whether this service has its attributes for
    // supportedKeyFormats or supportedKeyClasses set
    // if null, the values have not been initialized
    // if TRUE, at least one of supportedFormats/Classes is non null
    private volatile Boolean hasKeyAttributes;

    // supported encoding formats
    private String[] supportedFormats;

    // names of the supported key (super) classes
    private Class[] supportedClasses;

    // whether this service has been registered with the Provider
    private boolean registered;

    private static final Class<?>[] CLASS0 = new Class<?>[0];

    // this constructor and these methods are used for parsing
    // the legacy string properties.

    private Service(Provider provider) {
      this.provider = provider;
      aliases = Collections.<String>emptyList();
      attributes = Collections.<UString, String>emptyMap();
    }

    private boolean isValid() {
      return (type != null) && (algorithm != null) && (className != null);
    }

    private void addAlias(String alias) {
      if (aliases.isEmpty()) {
        aliases = new ArrayList<String>(2);
      }
      aliases.add(alias);
    }

    void addAttribute(String type, String value) {
      if (attributes.isEmpty()) {
        attributes = new HashMap<UString, String>(8);
      }
      attributes.put(new UString(type), value);
    }

    /**
     * Construct a new service.
     *
     * @param provider the provider that offers this service
     * @param type the type of this service
     * @param algorithm the algorithm name
     * @param className the name of the class implementing this service
     * @param aliases List of aliases or null if algorithm has no aliases
     * @param attributes Map of attributes or null if this implementation has no attributes
     * @throws NullPointerException if provider, type, algorithm, or className is null
     */
    public Service(Provider provider, String type, String algorithm,
        String className, List<String> aliases,
        Map<String, String> attributes) {
      if ((provider == null) || (type == null) ||
          (algorithm == null) || (className == null)) {
        throw new NullPointerException();
      }
      this.provider = provider;
      this.type = getEngineName(type);
      this.algorithm = algorithm;
      this.className = className;
      if (aliases == null) {
        this.aliases = Collections.<String>emptyList();
      } else {
        this.aliases = new ArrayList<String>(aliases);
      }
      if (attributes == null) {
        this.attributes = Collections.<UString, String>emptyMap();
      } else {
        this.attributes = new HashMap<UString, String>();
        for (Map.Entry<String, String> entry : attributes.entrySet()) {
          this.attributes.put(new UString(entry.getKey()), entry.getValue());
        }
      }
    }

    /**
     * Get the type of this service. For example, {@code MessageDigest}.
     *
     * @return the type of this service
     */
    public final String getType() {
      return type;
    }

    /**
     * Return the name of the algorithm of this service. For example,
     * {@code SHA-1}.
     *
     * @return the algorithm of this service
     */
    public final String getAlgorithm() {
      return algorithm;
    }

    /**
     * Return the Provider of this service.
     *
     * @return the Provider of this service
     */
    public final Provider getProvider() {
      return provider;
    }

    /**
     * Return the name of the class implementing this service.
     *
     * @return the name of the class implementing this service
     */
    public final String getClassName() {
      return className;
    }

    // internal only
    private final List<String> getAliases() {
      return aliases;
    }

    /**
     * Return the value of the specified attribute or null if this
     * attribute is not set for this Service.
     *
     * @param name the name of the requested attribute
     * @return the value of the specified attribute or null if the attribute is not present
     * @throws NullPointerException if name is null
     */
    public final String getAttribute(String name) {
      if (name == null) {
        throw new NullPointerException();
      }
      return attributes.get(new UString(name));
    }

    /**
     * Return a new instance of the implementation described by this
     * service. The security provider framework uses this method to
     * construct implementations. Applications will typically not need
     * to call it.
     *
     * <p>The default implementation uses reflection to invoke the
     * standard constructor for this type of service.
     * Security providers can override this method to implement
     * instantiation in a different way.
     * For details and the values of constructorParameter that are
     * valid for the various types of services see the
     * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
     * Java Cryptography Architecture API Specification &amp;
     * Reference</a>.
     *
     * @param constructorParameter the value to pass to the constructor, or null if this type of
     * service does not use a constructorParameter.
     * @return a new implementation of this service
     * @throws InvalidParameterException if the value of constructorParameter is invalid for this
     * type of service.
     * @throws NoSuchAlgorithmException if instantiation failed for any other reason.
     */
    public Object newInstance(Object constructorParameter)
        throws NoSuchAlgorithmException {
      if (registered == false) {
        if (provider.getService(type, algorithm) != this) {
          throw new NoSuchAlgorithmException
              ("Service not registered with Provider "
                  + provider.getName() + ": " + this);
        }
        registered = true;
      }
      try {
        EngineDescription cap = knownEngines.get(type);
        if (cap == null) {
          // unknown engine type, use generic code
          // this is the code path future for non-core
          // optional packages
          return newInstanceGeneric(constructorParameter);
        }
        if (cap.constructorParameterClassName == null) {
          if (constructorParameter != null) {
            throw new InvalidParameterException
                ("constructorParameter not used with " + type
                    + " engines");
          }
          Class<?> clazz = getImplClass();
          Class<?>[] empty = {};
          Constructor<?> con = clazz.getConstructor(empty);
          return con.newInstance();
        } else {
          Class<?> paramClass = cap.getConstructorParameterClass();
          if (constructorParameter != null) {
            Class<?> argClass = constructorParameter.getClass();
            if (paramClass.isAssignableFrom(argClass) == false) {
              throw new InvalidParameterException
                  ("constructorParameter must be instanceof "
                      + cap.constructorParameterClassName.replace('$', '.')
                      + " for engine type " + type);
            }
          }
          Class<?> clazz = getImplClass();
          Constructor<?> cons = clazz.getConstructor(paramClass);
          return cons.newInstance(constructorParameter);
        }
      } catch (NoSuchAlgorithmException e) {
        throw e;
      } catch (InvocationTargetException e) {
        throw new NoSuchAlgorithmException
            ("Error constructing implementation (algorithm: "
                + algorithm + ", provider: " + provider.getName()
                + ", class: " + className + ")", e.getCause());
      } catch (Exception e) {
        throw new NoSuchAlgorithmException
            ("Error constructing implementation (algorithm: "
                + algorithm + ", provider: " + provider.getName()
                + ", class: " + className + ")", e);
      }
    }

    // return the implementation Class object for this service
    private Class<?> getImplClass() throws NoSuchAlgorithmException {
      try {
        Reference<Class<?>> ref = classRef;
        Class<?> clazz = (ref == null) ? null : ref.get();
        if (clazz == null) {
          ClassLoader cl = provider.getClass().getClassLoader();
          if (cl == null) {
            clazz = Class.forName(className);
          } else {
            clazz = cl.loadClass(className);
          }
          if (!Modifier.isPublic(clazz.getModifiers())) {
            throw new NoSuchAlgorithmException
                ("class configured for " + type + " (provider: " +
                    provider.getName() + ") is not public.");
          }
          classRef = new WeakReference<Class<?>>(clazz);
        }
        return clazz;
      } catch (ClassNotFoundException e) {
        throw new NoSuchAlgorithmException
            ("class configured for " + type + " (provider: " +
                provider.getName() + ") cannot be found.", e);
      }
    }

    /**
     * Generic code path for unknown engine types. Call the
     * no-args constructor if constructorParameter is null, otherwise
     * use the first matching constructor.
     */
    private Object newInstanceGeneric(Object constructorParameter)
        throws Exception {
      Class<?> clazz = getImplClass();
      if (constructorParameter == null) {
        // create instance with public no-arg constructor if it exists
        try {
          Class<?>[] empty = {};
          Constructor<?> con = clazz.getConstructor(empty);
          return con.newInstance();
        } catch (NoSuchMethodException e) {
          throw new NoSuchAlgorithmException("No public no-arg "
              + "constructor found in class " + className);
        }
      }
      Class<?> argClass = constructorParameter.getClass();
      Constructor[] cons = clazz.getConstructors();
      // find first public constructor that can take the
      // argument as parameter
      for (Constructor<?> con : cons) {
        Class<?>[] paramTypes = con.getParameterTypes();
        if (paramTypes.length != 1) {
          continue;
        }
        if (paramTypes[0].isAssignableFrom(argClass) == false) {
          continue;
        }
        return con.newInstance(constructorParameter);
      }
      throw new NoSuchAlgorithmException("No public constructor matching "
          + argClass.getName() + " found in class " + className);
    }

    /**
     * Test whether this Service can use the specified parameter.
     * Returns false if this service cannot use the parameter. Returns
     * true if this service can use the parameter, if a fast test is
     * infeasible, or if the status is unknown.
     *
     * <p>The security provider framework uses this method with
     * some types of services to quickly exclude non-matching
     * implementations for consideration.
     * Applications will typically not need to call it.
     *
     * <p>For details and the values of parameter that are valid for the
     * various types of services see the top of this class and the
     * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
     * Java Cryptography Architecture API Specification &amp;
     * Reference</a>.
     * Security providers can override it to implement their own test.
     *
     * @param parameter the parameter to test
     * @return false if this this service cannot use the specified parameter; true if it can
     * possibly use the parameter
     * @throws InvalidParameterException if the value of parameter is invalid for this type of
     * service or if this method cannot be used with this type of service
     */
    public boolean supportsParameter(Object parameter) {
      EngineDescription cap = knownEngines.get(type);
      if (cap == null) {
        // unknown engine type, return true by default
        return true;
      }
      if (cap.supportsParameter == false) {
        throw new InvalidParameterException("supportsParameter() not "
            + "used with " + type + " engines");
      }
      // allow null for keys without attributes for compatibility
      if ((parameter != null) && (parameter instanceof Key == false)) {
        throw new InvalidParameterException
            ("Parameter must be instanceof Key for engine " + type);
      }
      if (hasKeyAttributes() == false) {
        return true;
      }
      if (parameter == null) {
        return false;
      }
      Key key = (Key) parameter;
      if (supportsKeyFormat(key)) {
        return true;
      }
      if (supportsKeyClass(key)) {
        return true;
      }
      return false;
    }

    /**
     * Return whether this service has its Supported* properties for
     * keys defined. Parses the attributes if not yet initialized.
     */
    private boolean hasKeyAttributes() {
      Boolean b = hasKeyAttributes;
      if (b == null) {
        synchronized (this) {
          String s;
          s = getAttribute("SupportedKeyFormats");
          if (s != null) {
            supportedFormats = s.split("\\|");
          }
          s = getAttribute("SupportedKeyClasses");
          if (s != null) {
            String[] classNames = s.split("\\|");
            List<Class<?>> classList =
                new ArrayList<>(classNames.length);
            for (String className : classNames) {
              Class<?> clazz = getKeyClass(className);
              if (clazz != null) {
                classList.add(clazz);
              }
            }
            supportedClasses = classList.toArray(CLASS0);
          }
          boolean bool = (supportedFormats != null)
              || (supportedClasses != null);
          b = Boolean.valueOf(bool);
          hasKeyAttributes = b;
        }
      }
      return b.booleanValue();
    }

    // get the key class object of the specified name
    private Class<?> getKeyClass(String name) {
      try {
        return Class.forName(name);
      } catch (ClassNotFoundException e) {
        // ignore
      }
      try {
        ClassLoader cl = provider.getClass().getClassLoader();
        if (cl != null) {
          return cl.loadClass(name);
        }
      } catch (ClassNotFoundException e) {
        // ignore
      }
      return null;
    }

    private boolean supportsKeyFormat(Key key) {
      if (supportedFormats == null) {
        return false;
      }
      String format = key.getFormat();
      if (format == null) {
        return false;
      }
      for (String supportedFormat : supportedFormats) {
        if (supportedFormat.equals(format)) {
          return true;
        }
      }
      return false;
    }

    private boolean supportsKeyClass(Key key) {
      if (supportedClasses == null) {
        return false;
      }
      Class<?> keyClass = key.getClass();
      for (Class<?> clazz : supportedClasses) {
        if (clazz.isAssignableFrom(keyClass)) {
          return true;
        }
      }
      return false;
    }

    /**
     * Return a String representation of this service.
     *
     * @return a String representation of this service.
     */
    public String toString() {
      String aString = aliases.isEmpty()
          ? "" : "\r\n  aliases: " + aliases.toString();
      String attrs = attributes.isEmpty()
          ? "" : "\r\n  attributes: " + attributes.toString();
      return provider.getName() + ": " + type + "." + algorithm
          + " -> " + className + aString + attrs + "\r\n";
    }

  }

}
